////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//            This file is distributed under the BSD 2-Clause open source license. See License.txt for details.               //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNScripting/inc/WNScopedVariableList.h"
#include "WNScripting/inc/WNScriptVariable.h"
#include "WNScripting/inc/WNScriptingEngineData.h"
#include "WNScripting/inc/WNFunctionManager.h"
#include "WNCore/inc/WNAssert.h"

#ifdef _WN_MSVC
    #pragma warning(push)
    #pragma warning(disable: 4800)
    #pragma warning(disable: 4146)
    #pragma warning(disable: 4355)
    #pragma warning(disable: 4244)
    #pragma warning(disable: 4267)

    #if _WN_MSVC < 160000000
        #pragma warning(disable: 4985)
    #endif
#endif


#ifdef _WN_ANDROID
    #include <llvm/Support/IRBuilder.h>
#else
    #include <llvm/IRBuilder.h>
#endif

#ifdef _WN_MSVC
    #pragma warning(pop)
#endif

#include <llvm/Module.h>
#include <llvm/Intrinsics.h>

using namespace WNScripting;

WNScriptVariable sInvalidVar = { WNInvalidType, WNInvalidIdentifier };

WN_VOID WNScopedVariableList::PushVariable(WNScriptVariable _ident) {
    WN_DEBUG_ASSERT(WNIsValid(_ident));
    mScopedVariables.push_back(_ident);
}

WN_VOID WNScopedVariableList::PushScopeBlock(WNScriptingEngineData& _scriptingData) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder);
    WNScriptVariable var = {WNInvalidType, WNInvalidIdentifier};
    llvm::Function* fnc = llvm::Intrinsic::getDeclaration(_scriptingData.mModule, llvm::Intrinsic::stacksave);
    var.mLlvmAlloca = builder->CreateCall(fnc, "");
    mScopedVariables.push_back(var);
}

WN_VOID WNScopedVariableList::PopScopeBlock(WNScriptingEngineData& _scriptingData, WN_BOOL _cleanStack) {
    llvm::IRBuilder<>* builder = reinterpret_cast<llvm::IRBuilder<>* >(_scriptingData.mBuilder);
    
    WN_INT32 i = static_cast<WN_INT32>(mScopedVariables.size());
    i -= 1;
    for(;i >= 0; --i) {
        if (!WNIsValid(mScopedVariables[i])) {
            break;
        }
    }

    WN_DEBUG_ASSERT(i >= 0);
    if (_cleanStack) {
        llvm::Function* fnc = llvm::Intrinsic::getDeclaration(_scriptingData.mModule, llvm::Intrinsic::stackrestore);
        builder->CreateCall(fnc, mScopedVariables[i].mLlvmAlloca, "");
    }
    mScopedVariables.erase(mScopedVariables.begin() + i, mScopedVariables.end());
}

WN_VOID WNScopedVariableList::PushGlobalVariable(WNScriptVariable _ident) {
    WN_DEBUG_ASSERT(WNIsValid(_ident));
    mScopedVariables.push_front(_ident);
}

const WNScriptVariable& WNScopedVariableList::GetVariable(WNIdentifierElement _varName) {
   std::deque<WNScriptVariable>::reverse_iterator i = mScopedVariables.rbegin();
   for(;i != mScopedVariables.rend(); ++i) {
       if (i->mName == _varName) {
           return(*i);
       }
   }
   return(sInvalidVar);
}

WN_VOID WNScopedVariableList::PushFunction(WNFunctionDecl* _functionDecl) {
    WN_DEBUG_ASSERT(WNIsValid(_functionDecl));
    mScopedFunctions.push_front(_functionDecl);
}

WN_VOID WNScopedVariableList::PopFunction() {
    WN_DEBUG_ASSERT(mScopedFunctions.size() != 0);
    mScopedFunctions.pop_front();
}

WNFunctionDecl* WNScopedVariableList::GetCurrentFunction() {
    WN_DEBUG_ASSERT(mScopedFunctions.size() != 0);
    return(*mScopedFunctions.begin());
}
